/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package gr.uop.cst.context.base;

import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

/**
 *
 * @author Ioannis Bourlakos &lt;mi49@uop.gr&gt;
 */
public class ContextProvider<T> implements Serializable {
    // TODO: make sure about visibility and synchronization rules in here

    public static final ReversibleContextSourceComparator SORT_BY_CONFIDENCE = new ReversibleContextSourceComparator() {

        @Override
        public int initialCompare(ContextSource o1, ContextSource o2) {
            double c1 = o1.getConfidence();
            double c2 = o2.getConfidence();
            if (c1 < c2) {
                return 1;
            }
            if (c1 == c2) {
                return 0;
            }
            return -1;
        }
    };
    public static final ReversibleContextSourceComparator SORT_BY_REFRESH_PERIOD = new ReversibleContextSourceComparator() {

        @Override
        public int initialCompare(ContextSource o1, ContextSource o2) {
            long p1 = o1.getMinimumRefreshPeriod();
            long p2 = o2.getMinimumRefreshPeriod();
            if (p1 < p2) {
                return -1;
            }
            if (p1 == p2) {
                return 0;
            }
            return 1;
        }
    };
    public static final ReversibleContextSourceComparator SORT_BY_AVAILABILITY = new ReversibleContextSourceComparator() {

        @Override
        public int initialCompare(ContextSource o1, ContextSource o2) {
            if (o1.isAvailable() && (!o2.isAvailable())) {
                return -1;
            }
            if (o1.isAvailable()) {
                return 0; // both o1 and o2 work!
            }
            return 1; // o1 doesn't work...
        }
    };
    public final ValueTransformation<T, T> identityTransformation = new ValueTransformation<T, T>() {

        @Override
        public T transform(T sourceValue) {
            return sourceValue;
        }
    };
    private final PriorityQueue<ContextSource> sourcesQueue;
    private final Map<ContextSource, ValueTransformation<T, ?>> sourcesToTransformations;
    private transient volatile ContextSource currentSource;
    private transient volatile T currentValue;

    public ContextProvider(ReversibleContextSourceComparator sourcesPriorityRule) {
        this.sourcesQueue = new PriorityQueue<ContextSource>(1, sourcesPriorityRule);
        this.sourcesToTransformations = new HashMap<ContextSource, ValueTransformation<T, ?>>();
    }

    public ContextProvider() {
        this(SORT_BY_AVAILABILITY);
    }

    private ContextSource getPrimarySource() throws ContextProviderUnavailableException {
        ContextSource result = sourcesQueue.poll();
        if (result == null) {
            throw new ContextProviderUnavailableException("No source assigned yet.");
        }
        // TODO: make sure comparators work as intended
        sourcesQueue.offer(result); // re-entry in case the priority demands re-organization
        return result;
    }

    public synchronized final T getCurrentValue() throws ContextProviderUnavailableException {
        ContextSource source = getPrimarySource();
        this.currentSource = source;
        this.currentValue = getValueFrom(source);
        return this.currentValue;
    }

    private T getValueFrom(ContextSource source) {
        ValueTransformation transformation = sourcesToTransformations.get(source);
        Object sourceValue = source.getValue();
        T result = (T) transformation.transform(sourceValue);
        return result;
    }

    public synchronized final ContextSource[] getContextSources() {
        return sourcesToTransformations.keySet().toArray(new ContextSource[0]);
    }

    public synchronized final <S> void addContextSource(ContextSource<S> newSource, ValueTransformation<T, S> transform) {
        sourcesQueue.add(newSource);
        sourcesToTransformations.put(newSource, transform);
    }

    public synchronized final void addContextSource(ContextSource<T> newSource) {
        this.addContextSource(newSource, identityTransformation);
    }

    public synchronized final void removeContextSource(ContextSource oldSource) {
        sourcesQueue.remove(oldSource);
        sourcesToTransformations.remove(oldSource);
    }

    public synchronized final ContextSource getCurrentSource() {
        return this.currentSource;
    }
    
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
    }

}
